So, C# doesn’t allow us to change the return type when overriding methods. Could we do it in IL?
For reference, here is a C# version of the initial example in my previous blog post, and the resulting IL class & method signatures generated by the C# compiler:
1 |
public class A { public virtual object Method() { /* ... */ }}public class B : A { public override object Method() { /* ... */ }}.class public auto ansi beforefieldinit A extends [mscorlib]System.Object { .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { /* ... */ }.method public hidebysig newslot virtual instance object Method() cil managed { /* ... */ }}.class public auto ansi beforefieldinit B extends A { .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { /* ... */ } // implicitly overrides A.Method.method public hidebysig virtual instance object Method() cil managed { /* ... */ }} |
Attempt 1: Implicit method overrides in IL
To help investigate this problem, I’ve created a small IL program that demonstrates the virtual method dispatch we need to subvert. Here is the output of the example without override covariance:
1 |
Calling A.Method on A: System.ObjectCalling B.Method on B: System.StringCalling A.Method on B: System.String |
To call a method in IL, you need to use the call
instruction, which just calls the method directly, or callvirt
, which calls the method with virtual dispatch. For example, to call System.Object.ToString()
on an object currently on the stack, the instruction is:
1 |
callvirt instance string [mscorlib]System.Object::ToString() |
Here we can immediately see a problem – the method token indicating which method to call includes the return type (this is why methods can be overridden by return type in IL). And, as we can see above, IL method overridding is done implicitly by signature, by missing out the newslot
keyword in the method declaration.
It follows that if we change B.Method
to return a string (example code), dynamic dispatch doesn’t work anymore, as it is now a completely different method to A.Method
:
1 |
Calling A.Method on A: System.ObjectCalling B.Method on B: System.StringCalling A.Method on B: System.Object |
Attempt 2: Explicit method overrides in IL
OK, so implicit overriding won’t work. However, in IL there is an .override
directive that can be applied to methods, which specifies explicit method overrides. Could we use this to force covariant method overrides by adding this to the B.Method declaration (example here)?
1 |
.method public virtual instance string Method() cil managed { .override A::Method /* ... */ ret} |
Unfortunately not – although it compiles ok, the resulting executable fails verification, and throws an exception when you try to run it:
1 |
System.MissingMethodException: Method not found: 'System.String A.Method()' |
So it seems we can’t do exactly what we want using IL.
Taking a step back
Might we be able to create something that has the same behaviour as covariant method overrides without actually overriding directly? A workaround currently exists in C# for interfaces where you can create methods that behave the same as covariant interface methods using explicit implementations:
1 |
public interface IA { object Method();}public class B : IA { public string Method() { /* ... */ } object IA.Method() { return Method(); }}// when using these classes:IA a = new B();B b = new B();object ao = a.Method();string bo = b.Method(); |
If we could do something similar with class overrides, we would be able to simulate covariant method overrides in the same way. In my next post, I’ll be looking at ways of doing exactly that.
Load comments